package com.wonders.frame.service.Core;

import com.gcr.util.StringUtil;
import com.wonders.frame.common.Enum.RemovedCodeEnum;
import com.wonders.frame.common.Enum.SessionKeyEnum;
import com.wonders.frame.common.Enum.SortSplitChar;
import com.wonders.frame.common.Enum.UriParamKeyEnum;
import com.wonders.frame.common.QueryForm;
import com.wonders.frame.common.Sorts;
import com.wonders.frame.common.util.SpringContextUtil;
import com.wonders.frame.core.entity.EntityTemplate;
import com.wonders.frame.entity.AuthAccount;
import com.wonders.frame.entity.AuthOrga;
import com.wonders.frame.entity.AuthPermission;
import com.wonders.frame.entity.AuthRole;
import com.wonders.frame.repository.AuthOrgaRepository;
import com.wonders.frame.repository.core.CoreRepository;
import com.wonders.frame.service.AuthOrgaService;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import javax.persistence.criteria.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.transaction.Transactional;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.*;

import static org.apache.commons.lang3.StringUtils.split;
import static org.apache.commons.lang3.StringUtils.substringBefore;
import static org.apache.commons.lang3.reflect.MethodUtils.invokeMethod;


public abstract class CoreCurdService<T extends EntityTemplate> {

    protected abstract CoreRepository<T> getRepository();

    private static final String SPLIT_CHAR = "#";
    private static final String REMOVED = "removed_eq";
    private static final String ID = "id_eq";
    public static final String FATCH_ROLE_PER = "fatchRoleRer";


    @Transactional
    public Page<T> findAllByParams(QueryForm form, HttpServletRequest request, List<String> fatch) {
        if (request != null) {
            Map<String, Object> map = initRolePermission(form.getParams(), request);
            form.getParams().putAll(map);
        }
        Specification<T> specification = getSpec(form, fatch);
        Pageable pageable = getPage(form);
        return getRepository().findAll(specification, pageable);
    }

    /**
     * @return
     * @Author 顾乘瑞
     * @Description 需要获取session
     * @Date 2019/4/19
     * @Param
     */
    public Page<T> findAllByParams(QueryForm form, HttpServletRequest request) {
        return findAllByParams(form, request, null);
    }

    /**
     * @return
     * @Author 顾乘瑞
     * @Description 需要懒加载
     * @Date 2019/4/19
     * @Param
     */
    public Page<T> findAllByParams(QueryForm form, List<String> fatch) {
        return findAllByParams(form, null, fatch);
    }

    public Page<T> findAllByParams(QueryForm form) {
        return findAllByParams(form, null, null);
    }


    @Transactional
    public List<T> findAllByParams(Map<String, Object> map, HttpServletRequest request, List<String> fatch) {
        map.putAll(initRolePermission(map, request));
        return getRepository().findAll(getSpec(map, fatch));
    }

    public List<T> findAllByParams(Map<String, Object> map, HttpServletRequest request) {
        return findAllByParams(map, request, null);
    }

    public List<T> findAllByParams(Map<String, Object> map) {
        return findAllByParams(map, null, null);
    }

    public List<T> findAllByParams(Map<String, Object> map, List<String> fatch) {
        return findAllByParams(map, null, fatch);
    }

    /**
     * @return
     * @Author 顾乘瑞
     * @Description 根据role过滤数据
     * @Date 2019/4/1
     * @Param
     */
    private Map<String, Object> initRolePermission(Map<String, Object> map, HttpServletRequest request) {
        boolean flag = true;
        Map<String, Object> roleMap = new HashMap<String, Object>();
        if (request == null) {
            return roleMap;
        }
        String uri = request.getRequestURI();
        HttpSession session = request.getSession();
        List<AuthPermission> ps = (List<AuthPermission>) session.getAttribute(SessionKeyEnum.API_PERMISSION.name());
        AuthAccount acc = (AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name());
        if (ps == null) {
            return roleMap;
        }

        String[] uris = {};
        String componentKey = "";
        Object value = null;
        for (AuthPermission p : ps) {
            uris = p.getComponentLinkUrl().split(SPLIT_CHAR);

            for (int i = 0; i < uris.length; i++) {
                if (Objects.equals(uris[i], uri)) {
                    for (AuthRole role : acc.getRoles()) {
                        if (Objects.equals(role.getRoleCode(), "admin")) {
                            break;
                        }
                    }
                    componentKey = p.getComponentKey();
                    if (Objects.equals(componentKey, UriParamKeyEnum.SESSION_ORGAID.name())) {
                        componentKey = UriParamKeyEnum.SESSION_ORGAID.getCode();
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        value = orgaId;
                    } else if (Objects.equals(componentKey, UriParamKeyEnum.SESSION_ORGAID_LIKE1.name())) {
                        componentKey = UriParamKeyEnum.SESSION_ORGAID_LIKE1.getCode();
                        AuthOrgaService orgaService = (AuthOrgaService) SpringContextUtil.getBeanByClass(AuthOrgaService.class);
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        value = orgaService.getLikeOrga1(orgaId);
                    } else if (Objects.equals(componentKey, UriParamKeyEnum.SESSION_ORGAID_LIKE2.name())) {
                        componentKey = UriParamKeyEnum.SESSION_ORGAID_LIKE2.getCode();
                        AuthOrgaService orgaService = (AuthOrgaService) SpringContextUtil.getBeanByClass(AuthOrgaService.class);
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        value = orgaService.getLikeOrga2(orgaId);
                    } else if (Objects.equals(componentKey, UriParamKeyEnum.ZYGL_KEY1.name())) {
                        componentKey = UriParamKeyEnum.ZYGL_KEY1.getCode();
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        AuthOrgaRepository repository = (AuthOrgaRepository) SpringContextUtil.getBeanByClass(AuthOrgaRepository.class);
                        AuthOrga authOrga = repository.getOne(orgaId);
                        if (StringUtils.isEmpty(authOrga.getOrgaArea())) {
                            value = null;
                        } else {
                            //匹配fly_job area字段前两位
                            value = authOrga.getOrgaArea().substring(0, 2);
                        }

                    } else if (Objects.equals(componentKey, UriParamKeyEnum.ZYGL_KEY2.name())) {
                        componentKey = UriParamKeyEnum.ZYGL_KEY2.getCode();
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        AuthOrgaRepository repository = (AuthOrgaRepository) SpringContextUtil.getBeanByClass(AuthOrgaRepository.class);
                        AuthOrga authOrga = repository.getOne(orgaId);
                        if (authOrga == null) {
                            value = null;
                        } else {
                            //匹配fly_job area字段前六位
                            value = authOrga.getOrgaArea().substring(0, 6);
                        }

                    } else if (Objects.equals(componentKey, UriParamKeyEnum.ZYGL_KEY3.name())) {
                        componentKey = UriParamKeyEnum.ZYGL_KEY3.getCode();
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        value = orgaId;
                    } else if (Objects.equals(componentKey, UriParamKeyEnum.SUBTASK_KEY1.name())) {
                        componentKey = UriParamKeyEnum.SUBTASK_KEY1.getCode();
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        AuthOrgaRepository repository = (AuthOrgaRepository) SpringContextUtil.getBeanByClass(AuthOrgaRepository.class);
                        AuthOrga authOrga = repository.getOne(orgaId);
                        if (StringUtils.isEmpty(authOrga.getOrgaArea())) {
                            value = null;
                        } else {
                            //匹配fly_job area字段前两位
                            value = authOrga.getOrgaArea().substring(0, 2);
                        }

                    } else if (Objects.equals(componentKey, UriParamKeyEnum.SUBTASK_KEY2.name())) {
                        componentKey = UriParamKeyEnum.SUBTASK_KEY2.getCode();
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        AuthOrgaRepository repository = (AuthOrgaRepository) SpringContextUtil.getBeanByClass(AuthOrgaRepository.class);
                        AuthOrga authOrga = repository.getOne(orgaId);
                        if (authOrga == null) {
                            value = null;
                        } else {
                            //匹配fly_job area字段前六位
                            value = authOrga.getOrgaArea().substring(0, 6);
                        }

                    }else if (Objects.equals(componentKey, UriParamKeyEnum.SUBTASK_KEY3.name())) {
                        componentKey = UriParamKeyEnum.SUBTASK_KEY3.getCode();
                        String orgaId = ((AuthAccount) session.getAttribute(SessionKeyEnum.USERINFO.name())).getOrgaId();
                        value = orgaId;
                    } else {
                        value = p.getComponentText();
                    }
                    if (map.containsKey(componentKey)) {
                        break;
                    }
                    if (flag) {
                        roleMap.put(componentKey, value);
                    }
                    break;
                }
            }
        }

        return roleMap;
    }

    private Specification<T> getSpec(Map<String, Object> map, List<String> fatch) {
        QueryForm form = new QueryForm();
        form.setParams(map);
        return getSpec(form, fatch);
    }

    private Specification<T> getSpec(QueryForm form, List<String> fatch) {
        form.getParams().put(REMOVED, RemovedCodeEnum.AVAILABLE.getCode());
        Specification<T> specification = initSpecification(form, fatch);
        return specification;
    }


    public T findOneByParams(Map<String, Object> map) {
        return findOneByParams(map, null);
    }

    @Transactional
    public T findOneByParams(Map<String, Object> map, List<String> fatch) {
        QueryForm form = new QueryForm();
        map.put(REMOVED, RemovedCodeEnum.AVAILABLE.getCode());
        form.setParams(map);
        Specification<T> specification = initSpecification(form, fatch);
        List<T> l = getRepository().findAll(specification);
        if (l != null && l.size() > 0) {
            return l.get(0);
        }
        return null;
    }

    public T findById(String id) {
        return findById(id, null);
    }

    public T findById(String id, List<String> fatch) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(ID, id);
        map.put(REMOVED, RemovedCodeEnum.AVAILABLE.getCode());
//        QueryForm form = new QueryForm();
//        form.setParams(map);
//        if (findOneByParams(map, fatch).size() > 0) {
//            return findAllByParams(form, null, fatch).getContent().get(0);
//        }
        return findOneByParams(map, fatch);
    }


    //@Changes(changeType = ChangeType.DELETE)
    public void deleteById(String id) {
        T t = getRepository().getOne(id);
        t.setRemoved(RemovedCodeEnum.REMOVED.getCode());
        getRepository().save(t);
    }

    private Specification initSpecification(QueryForm form, List<String> fatch) {
        return new Specification<T>() {

            /**
             * 构造断言
             * @param root 实体对象引用
             * @param query 规则查询对象
             * @param cb 规则构建对象
             * @return 断言
             */
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicates = null;
                try {
                    predicates = initParams(form, root, query, cb);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return cb.and(predicates.toArray(new Predicate[0]));
            }

            private List<Predicate> initParams(QueryForm form, Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) throws Exception {
                List<Predicate> ps = new ArrayList<Predicate>();
                for (String key : form.getParams().keySet()) {
                    String name = substringBefore((String) key, "_");
                    String operator = key.toString().substring(key.toString().lastIndexOf("_") + 1);
                    Object val = form.getParams().get(key);
                    if (initPredicate(cb, root, name, operator, val, fatch) != null) {
                        ps.add(initPredicate(cb, root, name, operator, val, fatch));
                    }

                }
                return ps;
            }

            private Predicate initPredicate(CriteriaBuilder builder, Root<T> root,
                                            String name, String operator, Object val, List<String> fatch)
                    throws ParseException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {

                if (fatch != null && fatch.size() > 0) {

                    for (String fa : fatch) {
                        //TODO
//                        多层级联暂时没找到好方法，临时这么写下
                        if (Objects.equals(FATCH_ROLE_PER, fa)) {
                            root.fetch("user", JoinType.INNER);
                            root.fetch("roles", JoinType.INNER).fetch("pres", JoinType.INNER);
                            continue;
                        }
                        root.fetch(fa, JoinType.INNER);

                    }
                }

                if (val == null) {
                    return null;
                }
                if (val instanceof String) {
                    if (StringUtil.isEmpty((String) val)) {
                        return null;
                    }
                }
                String[] names = split(name, "-");
                Path path;
                if (names.length != 0) {
                    path = root.get(names[0]);
                    for (int i = 1; i < names.length; i++) {
                        path = path.get(names[i]);
                    }
                } else {
                    path = root.get(name);
                }


                if ("eq".equals(operator)) {
                    return builder.equal(path, val);
                } else if ("isNull".equals(operator)) {
                    return builder.isNull(path);
                } else if ("isNotNull".equals(operator)) {
                    return builder.isNotNull(path);
                } else if ("like".equals(operator)) {
                    return builder.like(path, "%" + val + "%");
                } else if ("likeR".equals(operator)) {
                    return builder.like(path, val + "%");
                } else if ("likeL".equals(operator)) {
                    return builder.like(path, "%" + val);
                } else if ("gt".equals(operator)) {
                    return builder.greaterThan(path, (Comparable) val);
                } else if ("lt".equals(operator)) {
                    return builder.lessThan(path, (Comparable) val);
                } else if ("ge".equals(operator)) {
                    return builder.greaterThanOrEqualTo(path, (Comparable) val);
                } else if ("le".equals(operator)) {
                    return builder.lessThanOrEqualTo(path, (Comparable) val);
                } else if ("gtFormatDate".equals(operator)) {
                    return builder.greaterThan(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd") );
                } else if ("ltFormatDate".equals(operator)) {
                    return builder.lessThan(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd") );
                } else if ("geFormatDate".equals(operator)) {
                    return builder.greaterThanOrEqualTo(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd") );
                } else if ("leFormatDate".equals(operator)) {
                    return builder.lessThanOrEqualTo(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd") );
                } else if ("gtFormatDateTime".equals(operator)) {
                    return builder.greaterThan(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd HH:mm:ss") );
                } else if ("ltFormatDateTime".equals(operator)) {
                    return builder.lessThan(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd HH:mm:ss") );
                } else if ("geFormatDateTime".equals(operator)) {
                    return builder.greaterThanOrEqualTo(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd HH:mm:ss") );
                } else if ("leFormatDateTime".equals(operator)) {
                    return builder.lessThanOrEqualTo(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd HH:mm:ss") );
                } else if ("sgt".equals(operator)) {
                    return builder.greaterThan(path, (Comparable) val);
                } else if ("slt".equals(operator)) {
                    return builder.lessThan(path, (Comparable) val);
                } else if ("sge".equals(operator)) {
                    return builder.greaterThanOrEqualTo(path, (Comparable) val);
                } else if ("sle".equals(operator)) {
                    return builder.lessThanOrEqualTo(path, (Comparable) val);
                } else if ("dgt".equals(operator)) {
                    val = val + " 00:00:00";
                    return builder.greaterThan(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd HH:mm:ss"));
                } else if ("dlt".equals(operator)) {
                    val = val + " 23:59:59";
                    return builder.lessThan(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd HH:mm:ss"));
                } else if ("dge".equals(operator)) {
                    val = val + " 00:00:00";
                    return builder.greaterThanOrEqualTo(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd HH:mm:ss"));
                } else if ("dle".equals(operator)) {
                    val = val + " 23:59:59";
                    return builder.lessThanOrEqualTo(path, DateUtils.parseDate(val.toString(), "yyyy-MM-dd HH:mm:ss"));
                } else if ("in".equals(operator)) {
                    CriteriaBuilder.In<Object> in = builder.in(path);
                    String[] vs = split((String) val, ',');
                    for (int i = 0; i < vs.length; i++) {
                        in.value(vs[i]);
                    }

                    return in;
                } else if ("ne".equals(operator)) {
                    return builder.notEqual(path, val);
                }

                if (StringUtils.startsWith(operator, "is")) {
                    return (Predicate) invokeMethod(builder, operator, path);
                }

                return (Predicate) invokeMethod(builder, operator, path, val);
            }
        };
    }

    private Pageable getPage(QueryForm form) {
        Sort sort;
        List<Sorts> list=form.getSorts();

        if(CollectionUtils.isEmpty(list)){
            sort = new Sort(Sort.Direction.DESC, "updDate");
            return PageRequest.of(form.getPageNum() - 1, form.getPageSize(), sort);
        }


        if(list.size()>0){
            List<Sort.Order> sortList=new ArrayList<>();
            for (Sorts sorts:list){
                if("asc".equals(sorts.getDirection().toLowerCase())){
                    sortList.add(new Sort.Order(Sort.Direction.ASC,sorts.getField()));
                }else{
                    sortList.add(new Sort.Order(Sort.Direction.DESC,sorts.getField()));
                }
            }

            return PageRequest.of(form.getPageNum() - 1, form.getPageSize(),  Sort.by(sortList));
        }

        sort = new Sort(Sort.Direction.DESC, "updDate");
        return PageRequest.of(form.getPageNum() - 1, form.getPageSize(), sort);
    }
}
